# -*- coding: UTF-8 -*-
# 使用6.6.1版本的API
import shutil
from function import *
from ws_server import SimpleEcho, startWbSocket


# 导入策略模块


class CFtdcMdSpi(mdapi.CThostFtdcMdSpi):

    def __init__(self, mduserapi):
        mdapi.CThostFtdcMdSpi.__init__(self)
        self.mduserapi = mduserapi

    # 当客户端与交易后台建立起通信连接时（还未登录前），该方法被调用
    def OnFrontConnected(self):
        timePrintLog("开始建立行情连接")
        # 发送用户登录请求
        loginfield = mdapi.CThostFtdcReqUserLoginField()
        loginfield.BrokerID = g.broker_id
        loginfield.UserID = g.investorID
        loginfield.Password = g.password

        ret = self.mduserapi.ReqUserLogin(loginfield, 0)

        if ret == 0:
            timePrintLog('发送用户登录行情账户请求成功！')
        else:
            timePrintLog('发送用户登录行情账户请求失败！')
            judge_ret(ret)

    def OnRspUserLogin(self, pRspUserLogin, pRspInfo, nRequestID, bIsLast):
        if pRspInfo.ErrorID != 0 and pRspInfo != None:
            timePrintLog('行情连接失败\n错误信息为：{}\n错误代码为：{}'.format(pRspInfo.ErrorMsg, pRspInfo.ErrorID))
        else:
            timePrintLog('行情账户登录成功！')
            timePrintLog('已开启新线程传递tick至策略')
            t0 = Thread(target=get_tick)
            t0.start()

            t1 = Thread(target=startWbSocket)
            t1.start()


            # timePrintLog('已开启新线程传递Bar至策略')
            # t1 = Thread(target=get_Bar)
            # t1.start()

        # # 订阅行情
        # ret = self.mduserapi.SubscribeMarketData([id.encode('utf-8') for id in g.subID],
        #                                          len(g.subID))
        # if ret == 0:
        #     timePrintLog('发送订阅合约请求成功！')
        # else:
        #     timePrintLog('发送订阅合约请求失败！')
        #     judge_ret(ret)

    # 订阅行情应答函数
    def OnRspSubMarketData(self, pSpecificInstrument, pRspInfo, nRequestID, bIsLast):
        if pRspInfo.ErrorID != 0 and pRspInfo != None:
            timePrintLog(
                '订阅行情失败！\n订阅代码：{}\n错误信息为：{}\n错误代码为：{}'.format(pSpecificInstrument.InstrumentID, pRspInfo.ErrorMsg,
                                                              pRspInfo.ErrorID))
        else:
            timePrintLog('订阅{}行情成功！'.format(pSpecificInstrument.InstrumentID))
        # if bIsLast:
        #     timePrintLog('已开启新线程传递tick至策略')
        #     t0 = Thread(target=get_tick)
        #     t0.start()
        #
        #     timePrintLog('已开启新线程传递Bar至策略')
        #     t1 = Thread(target=get_Bar)
        #     t1.start()

    # 深度行情通知
    def OnRtnDepthMarketData(self, pDepthMarketData):
        # 如果是无效数据，退出
        try:
            if clean_data(pDepthMarketData):
                timePrintLog('clean_data')
                return
                # 保存数据时不更新数据
            if g.save_data_flag:
                return
            timePrintLog(marketData2dict(pDepthMarketData))
            g.tickQueue.put(pDepthMarketData)
        except Exception as e:
            red_printLog(e)
            return





class CTraderSpi(tdapi.CThostFtdcTraderSpi):
    def __init__(self, tduserapi):
        tdapi.CThostFtdcTraderSpi.__init__(self)
        self.tduserapi = tduserapi

    # 连接前台
    def OnFrontConnected(self):
        timePrintLog("开始建立交易连接")
        authfield = tdapi.CThostFtdcReqAuthenticateField()
        authfield.BrokerID = g.broker_id
        authfield.UserID = g.investorID
        authfield.AppID = g.appID
        authfield.AuthCode = g.authcode
        ret = self.tduserapi.ReqAuthenticate(authfield, 0)
        if ret == 0:
            timePrintLog('发送穿透式认证请求成功！')
        else:
            timePrintLog('发送穿透式认证请求失败！')
            judge_ret(ret)

    # 穿透式认证响应
    def OnRspAuthenticate(self, pRspAuthenticateField, pRspInfo, nRequestID: 'int', bIsLast: 'bool'):
        if pRspInfo.ErrorID != 0 and pRspInfo != None:
            timePrintLog('穿透式认证失败\n错误信息为：{}\n错误代码为：{}'.format(pRspInfo.ErrorMsg, pRspInfo.ErrorID))
        else:
            timePrintLog('穿透式认证成功！')

            # 发送登录请求
            loginfield = tdapi.CThostFtdcReqUserLoginField()
            loginfield.BrokerID = g.broker_id

            loginfield.UserID = g.investorID
            loginfield.Password = g.password

            ret = self.tduserapi.ReqUserLogin(loginfield, 0)
            if ret == 0:
                timePrintLog('发送登录交易账户成功！')
            else:
                timePrintLog('发送登录交易账户失败！')
                judge_ret(ret)

    # 用户登录结果返回
    def OnRspUserLogin(self, pRspUserLogin, pRspInfo, nRequestID, bIsLast):
        if pRspInfo.ErrorID != 0 and pRspInfo != None:
            timePrintLog('登录交易账户失败\n错误信息为：{}\n错误代码为：{}'.format(pRspInfo.ErrorMsg, pRspInfo.ErrorID))
        else:
            timePrintLog('登录交易账户成功！')
            # 保存数据用于下单
            g.frontID = pRspUserLogin.FrontID
            g.sessionID = pRspUserLogin.SessionID
            g.maxOrderRef = int(pRspUserLogin.MaxOrderRef)
            # 保存交易日
            g.tradingDay = pRspUserLogin.TradingDay
            g.tdLogin_flag = True



    def OnRspQryProduct(self, pProduct, pRspInfo, nRequestID, bIsLast):
        try:
            sec = pProduct.ProductID
            opt = '合约乘数'
            # 需要判断section是否存在，如果不存在会报错，option不需要检查是否存在
            if not g.productInfo.has_section(sec):
                g.productInfo.add_section(sec)
            g.productInfo.set(sec, opt, str(pProduct.VolumeMultiple))

            opt = '最小变动价位'
            g.productInfo.set(sec, opt, str(pProduct.PriceTick))

            if bIsLast:
                g.productInfo.write(open(g.productInfo_fileName, "w", encoding='utf-8'))
                timePrintLog('查询产品成功！')
        except Exception as e:
            timePrintLog(e)


    def OnRspQryInstrument(self, pInstrument, pRspInfo, nRequestID, bIsLast):
        g.ExchangeID[pInstrument.InstrumentID] = pInstrument.ExchangeID
        if bIsLast:
            with open('./con_file/ExchangeID.json', 'w', newline='\n', encoding='utf-8') as f:
                # json.dump(ExchangeID, f, ensure_ascii=False)
                data = json.dumps(g.ExchangeID, indent=4, ensure_ascii=False)
                f.write(data)
            timePrintLog('查询合约完成')

class CtpWang(object):
    def __init__(self, **kwargs):
        # 初始化需要订阅的合约和K线类型
        # init_subID()
        # 创建相关文件
        # create_tradeLogFile()
        # 查询版本
        timePrintLog(f'CTP API Version:{tdapi.CThostFtdcTraderApi_GetApiVersion()}')

    # 登录行情服务器
    def connect_to_md(self):
        # 创建API实例
        self.mduserapi = mdapi.CThostFtdcMdApi_CreateFtdcMdApi('./con_file/')
        # 创建spi实例
        self.mduserspi = CFtdcMdSpi(self.mduserapi)

        # 保存api和spi
        g.mduserapi = self.mduserapi
        g.mduserspi = self.mduserspi

        # 连接行情前置服务器
        self.mduserapi.RegisterFront(g.market_server_front)
        # 将spi注册给api
        self.mduserapi.RegisterSpi(self.mduserspi)
        # 第5步，API正式启动，dll底层会自动去连上面注册的地址
        self.mduserapi.Init()
        # join的目的是为了阻塞主线程
        # self.mduserapi.Join()

    # 登录交易账户
    def connect_to_td(self):
        # 创建API实例
        self.tduserapi = tdapi.CThostFtdcTraderApi_CreateFtdcTraderApi('./con_file/')

        # 创建SPI实例。CTraderSpi是继承自头文件中CThostFtdcTraderSpi的类型，
        # 用于收从CTP的回复，可以重写父类中的函数来实现自己的逻辑
        self.tduserspi = CTraderSpi(self.tduserapi)

        # 保存api和spi
        g.tduserapi = self.tduserapi
        g.tduserspi = self.tduserspi

        # 将创建的SPI实例注册进实例，这样该API实例发出的请求对应的回报就会回调到对应的SPI实例的函数
        self.tduserapi.RegisterSpi(self.tduserspi)

        # 订阅共有流与私有流。订阅方式主要有三种，分为断点续传，重传和连接建立开始传三种。
        # TERT_RESTART：从本交易日开始重传。
        # TERT_RESUME：从上次收到的续传。
        # TERT_QUICK：只传送登录后的内容。
        self.tduserapi.SubscribePrivateTopic(tdapi.THOST_TERT_QUICK)
        self.tduserapi.SubscribePublicTopic(tdapi.THOST_TERT_QUICK)

        # 注册前置地址，是指将CTP前置的IP地址注册进API实例内
        self.tduserapi.RegisterFront(g.trade_server_front)

        # API启动，init之后就会启动一个内部线程读写，并去连CTP前置
        self.tduserapi.Init()

        # Join函数是使得函数阻塞在这里，等待api实例创建的内部线程的结束。
        # 内部线程需要release才会释放结束
        # self.tduserapi.Join()

    def initInfo(self):
        # 查询并添加合约的交易所，用于下单
        self.qryInstrument()

    def qryProduct(self):
        queryFile = tdapi.CThostFtdcQryProductField()
        ret = self.tduserapi.ReqQryProduct(queryFile, 0)
        if ret == 0:
            timePrintLog('发送查询产品成功！')
        else:
            timePrintLog('发送查询产品失败！')
            judge_ret(ret)
            while ret != 0:
                queryFile = tdapi.CThostFtdcQryProductField()
                ret = self.tduserapi.ReqQryProduct(queryFile, 0)
                timePrintLog('正在查询产品...')
                time.sleep(5)
        time.sleep(1)

    def qryInstrument(self):
        queryFile = tdapi.CThostFtdcQryInstrumentField()
        ret = self.tduserapi.ReqQryInstrument(queryFile, 0)
        if ret == 0:
            timePrintLog('发送查询合约成功！')
        else:
            timePrintLog('发送查询合约失败！')
            judge_ret(ret)
            while ret != 0:
                queryFile = tdapi.CThostFtdcQryInstrumentField()
                ret = self.tduserapi.ReqQryInstrument(queryFile, 0)
                timePrintLog('正在查询合约...')
                time.sleep(5)
        time.sleep(1)

def main():
    # 如果不是7*24小时，判断是否开盘
    timePrintLog('正在登录的是：{}'.format(g.broker_name))
    if '7*24' not in g.broker_name:
        if not is_open():
            timePrintLog('今日未开盘')
            return
    ctp_wang = CtpWang()
    ctp_wang.connect_to_md()
    time.sleep(3)
    ctp_wang.connect_to_td()

    while not g.tdLogin_flag:
        timePrintLog('正在等待登录交易账户成功')
        time.sleep(5)


    ctp_wang.initInfo()



    # 创建所有合约对应的键值对
    g.subID = list(g.ExchangeID.keys())
    # g.subID = ['m2209']

    # 如果只订阅期货加下边这句
    g.subID = [i for i in g.subID if len(i)<= 6]

    ret = make_dir('./实时数据', str(g.tradingDay))
    # 创建记录行情的文件
    # 如果文件夹已存在，则不用新建文件，否则会将夜盘数据删除
    if ret == 0:
        for codeID in g.subID:
            with open('./实时数据/{}/{}.csv'.format(g.tradingDay, codeID), 'w',
                      newline='') as csvfile:  # 以写入模式打开csv文件，如果没有csv文件会自动创建。
                writer = csv.writer(csvfile)
                writer.writerow(['TradingDay', 'ExchangeID', 'LastPrice', 'PreSettlementPrice',
                                 'PreClosePrice', 'PreOpenInterest', 'OpenPrice', 'HighestPrice', 'LowestPrice',
                                 'Volume',
                                 'Turnover', 'OpenInterest', 'ClosePrice', 'SettlementPrice', 'UpperLimitPrice',
                                 'LowerLimitPrice',
                                 'PreDelta', 'CurrDelta', 'UpdateTime', 'UpdateMillisec', 'BidPrice1', 'BidVolume1',
                                 'AskPrice1',
                                 'AskVolume1', 'BidPrice2', 'BidVolume2', 'AskPrice2', 'AskVolume2', 'BidPrice3',
                                 'BidVolume3',
                                 'AskPrice3', 'AskVolume3', 'BidPrice4', 'BidVolume4', 'AskPrice4', 'AskVolume4',
                                 'BidPrice5',
                                 'BidVolume5', 'AskPrice5', 'AskVolume5', 'AveragePrice', 'ActionDay', 'InstrumentID',
                                 'ExchangeInstID', 'BandingUpperPrice', 'BandingLowerPrice'])  # 写入列名，如果没有列名可以不执行这一行

    # 订阅行情
    subscribeMarketData()

    while True:
        try:
            timePrintLog('等待')

            time.sleep(60)

            # # 保存数据
            # timePrintLog('开始保存数据')
            # g.save_data_flag = True
            #
            # # 压缩数据
            # getZipDir('./实时数据/{}'.format(g.tradingDay), './实时数据/{}.zip'.format(g.tradingDay))
            # ret = uploadToBaiDuYun('./实时数据/{}.zip'.format(g.tradingDay), '期货tick数据')
            # if ret == 0:
            #     timePrintLog('发送数据成功')
            # break

            if datetime.datetime.now().strftime('%H:%M') == '16:00':
                # 保存数据
                timePrintLog('开始压缩数据')
                g.save_data_flag = True

                # 压缩数据
                try:
                    getZipDir('./实时数据/{}'.format(g.tradingDay), './实时数据/{}.zip'.format(g.tradingDay))
                except Exception as e:
                    red_printLog(e)
                else:
                    timePrintLog('压缩成功')


                # 上传到百度云
                ret = uploadToBaiDuYun('./实时数据/{}.zip'.format(g.tradingDay), '期货tick数据')
                if ret == 0:
                    timePrintLog('发送数据成功')
                else:
                    timePrintLog('发送失败,失败原因是：{}'.format(ret))

                time.sleep(60)

                # 如果压缩文件存在，且没问题，删除文件夹
                try:
                    zf = zipfile.ZipFile('./实时数据/{}'.format(g.tradingDay))
                    zf.testzip()
                except Exception as e:
                    red_printLog(e)

                else:
                    shutil.rmtree('./实时数据/{}'.format(g.tradingDay))
                    timePrintLog('已删除文件夹：./实时数据/{}'.format(g.tradingDay))

                break

            if datetime.datetime.now().strftime('%H:%M') == '02:40':
                break
        except Exception as e:
            timePrintLog(e)


if __name__ == '__main__':
    main()
